namespace BM.Tools.WebControls.MForm.Generator
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Xml.Schema;

    using BM.Tools.Xml.Schema;

    /// <summary>
    /// FormItem is a base class for all those visited nodes that will have a corresponding node in the generated form.
    /// </summary>
    public abstract class FormItem : FormVisited
    {                
        private FormItem parentElement;
        
        private Dictionary<string, string> restrictions = new Dictionary<string, string>();

        private Collection<XmlSchemaIdentityConstraint> identityConstraints = new Collection<XmlSchemaIdentityConstraint>();
        
        private int maxLength = -1;

        private Collection<string> options = new Collection<string>();

        /// <summary>
        /// Initializes a new instance of the <see cref="FormItem"/> class. 
        /// </summary>
        /// <param name="builder">
        /// The builder.
        /// </param>
        /// <param name="parent">
        /// The parent.
        /// </param>
        /// <param name="schemaObject">
        /// The schemaObject.
        /// </param>
        protected FormItem(FormBuilder builder, Visited parent, XmlSchemaObject schemaObject) : base(builder, parent, schemaObject)
        {
            Enabled = true;
            Builder = builder;
        }

        /// <summary>
        /// Gets Builder.
        /// </summary>
        /// <value>
        /// The builder.
        /// </value>
        public FormBuilder Builder
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets Restrictions.
        /// </summary>
        /// <value>
        /// The xml schema simple restriction.
        /// </value>
        public Dictionary<string, string> Restrictions
        {
            get
            {
                return restrictions;
            }
        }

        /// <summary>
        /// Gets the primitive data type of the xsd element 
        /// </summary>
        public XmlTypeCode? DataType
        {
            get;
            internal set;
        }

        /// <summary>
        /// Gets or sets DefaultValue.
        /// </summary>
        /// <value>
        /// The default value.
        /// </value>
        public string DefaultValue
        {
            get;
            protected set;
        }

        /// <summary>
        /// Gets or sets FixedValue.
        /// </summary>
        /// <value>
        /// The fixed value.
        /// </value>
        public string FixedValue
        {
            get;
            protected set;
        }

        /// <summary>
        /// Gets IdentityConstraints.
        /// </summary>
        /// <value>
        /// The identity constraints.
        /// </value>
        public Collection<XmlSchemaIdentityConstraint> IdentityConstraints
        {
            get
            {
                return identityConstraints;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the node is terminal.
        /// </summary>
        /// <value>
        /// is terminal.
        /// </value>
        public bool IsLeaf
        {
            get;
            protected internal set;
        }

        /// <summary>
        /// Gets the information about the maximal length of the data associated with this item.        
        /// </summary>
        public int MaxLength
        {
            get
            {
                return maxLength;
            }

            internal set
            {
                maxLength = value;
            }
        }        

        /// <summary>
        /// Gets MaxOccurs.
        /// </summary>
        /// <value>
        /// The max occurs.
        /// </value>
        public override decimal MaxOccurs
        {
            get
            {
                var maxOccursBound = IsLeaf
                                             ? Builder.Settings.LeafMaxOccursBound
                                             : Builder.Settings.NonLeafMaxOccursBound;
                return Math.Min(MaxOccursFromSchema, maxOccursBound);                
            }
        }

        /// <summary>
        /// Gets MinOccurs.
        /// </summary>
        /// <value>
        /// The min occurs.
        /// </value>
        public override decimal MinOccurs
        {
            get
            {
                if (schemaObject is XmlSchemaChoice)
                {
                    return SchemaHelper.GetMinOccurs();
                }

                if (GetParentObject() is XmlSchemaParticle)
                {
                    var parentParticle = (XmlSchemaParticle)GetParentObject();
                    return parentParticle.MinOccurs * SchemaHelper.GetMinOccurs();
                }

                if (parent != null && parent.parent.schemaObject is XmlSchemaParticle)
                {
                    var grandParentParticle = (XmlSchemaParticle)parent.parent.schemaObject;
                    return grandParentParticle.MinOccurs * SchemaHelper.GetMinOccurs();
                }

                return SchemaHelper.GetMinOccurs();
            }
        }

        /// <summary>
        /// Gets Options.
        /// </summary>
        /// <value>
        /// The options.
        /// </value>
        public Collection<string> Options
        {
            get
            {
                return options;
            }

            internal set
            {
                options = value;
            }
        }

        /// <summary>
        /// Gets ParentElement.
        /// </summary>
        /// <value>
        /// The parent element.
        /// </value>
        public FormItem ParentElement
        {
            get
            {
                if (parentElement == null)
                {
                    parentElement = CalculateParentElement();
                }

                return parentElement;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the Form item is enabled.
        /// </summary>
        public bool Enabled
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the value of max occurs from the schema
        /// </summary>
        protected virtual decimal MaxOccursFromSchema
        {
            get
            {
                if (GetParentObject() is XmlSchemaParticle)
                {
                    var particle = (XmlSchemaParticle)GetParentObject();
                    if (parent.parent != null && parent.parent.schemaObject is XmlSchemaGroupBase)
                    {
                        var grandParentParticle = (XmlSchemaParticle)parent.parent.schemaObject;
                        return particle.MaxOccurs * grandParentParticle.MaxOccurs * SchemaHelper.GetMaxOccurs();
                    }

                    return particle.MaxOccurs * SchemaHelper.GetMaxOccurs();
                }

                return SchemaHelper.GetMaxOccurs();
            }
        }

        /// <summary>
        /// Adds a simple type restriction
        /// </summary>
        /// <param name="restrictionType">
        /// The restriction type.
        /// </param>
        /// <param name="restrictionValue">
        /// The restriction value.
        /// </param>
        public void AddRestriction(string restrictionType, string restrictionValue)
        {
            restrictions[restrictionType] = restrictionValue;
        }

        private FormItem CalculateParentElement()
        {
            var current = parent;
            var result = current as FormItem;
            while (result == null && current != null)
            {                
                current = current.parent;
                result = current as FormItem;
            }

            return result;
        }

        private XmlSchemaObject GetParentObject()
        {
            if (parent == null)
            {
                return null;
            }

            return parent.schemaObject;
        }
    }
}